using System.Collections.Generic;
using NetProtocol.Enum;
using UnityEngine;

namespace IQIGame.Onigao.GamePlay
{
    public class LevelPlayPushEntityCtrl : LevelPlaySubBaseCtrl
    {
        #region fields

        private LevelNormalEntity _entity;
        private Collider _collider;
        private int _logicEndPosX;
        private int _logicEndPosY;
        private int _logicEndPosZ;
        private Vector3 _moveDir;
        private float _speed;
        private Vector3 _curLogicPos;
        private float _acceleration;
        private const float TotalTime = 1f;
        private readonly Collider[] _collisionResult = new Collider[10];

        #endregion

        #region lifecycle

        protected override void OnModuleGameplayInit()
        {
        }

        protected override void OnSceneLoaded()
        {
            MsgDispatcher.AddListener<int, int>(MsgEventType.Level_PushEntity, OnPushEntity);
        }

        protected override void OnDispose()
        {
            MsgDispatcher.RemoveListener<int, int>(MsgEventType.Level_PushEntity, OnPushEntity);
        }

        #endregion lifecycle

        private void OnPushEntity(int entityId, int maxDistance)
        {
            StartPush(entityId, maxDistance);
        }

        private void StartPush(int entityId, int maxDistance)
        {
            if (regionCtrl.entityCtrl.entitys.GetValueOrDefault(entityId) is not
                LevelNormalEntity entity)
            {
                return;
            }

            _entity = entity;
            var moveComponent = entity.GetMoveComponent();
            moveComponent.StopMove();
            _collider = moveComponent.collider;
            var player = regionCtrl.entityCtrl.GetMainCharacter();
            var v2HeroToEntity = new Vector2(entity.x - player.x, entity.z - player.z);
            //计算推动方向
            var moveDir = GetPushDir(new Vector2(1, 0), entity.transform.forward, v2HeroToEntity);
            _moveDir = new Vector3(moveDir.x, 0, moveDir.y).normalized;
            //如果碰撞检测不通过，直接结束
            if (CollisionCheck())
            {
                EndPush((int) entity.x, (int) entity.y, (int) entity.z);
                return;
            }

            var endPos = entity.transform.position + _moveDir * GameMathUtil.LogicValue2Float(maxDistance);
            //确保最终位置在寻路网格内
            regionCtrl.navigationCtrl.TryMove(entity.transform.position, endPos,
                out var realEndPos);
            var realDistance = Mathf.Min(Vector3.Distance(entity.transform.position, realEndPos), maxDistance);
            //单位转为cm
            _logicEndPosX = Mathf.RoundToInt(GameMathUtil.Float2LogicValue(realEndPos.x));
            _logicEndPosZ = Mathf.RoundToInt(GameMathUtil.Float2LogicValue(realEndPos.z));
            _logicEndPosY = (int) entity.y;
            //初速度是平均速度的两倍 单位:cm/s
            _speed = GameMathUtil.Float2LogicValue(realDistance) / TotalTime * 2;
            //加速度,模拟摩擦力
            _acceleration = -_speed / TotalTime;
            _curLogicPos = new Vector3(entity.x, entity.y, entity.z);
        }

        private void EndPush(int x, int y, int z)
        {
            CLevelLogicModule.NetCore.CS_PushEntityArrive(_entity.id, x, y, z);
            _entity = null;
        }

        //参与计算的变量单位都是cm
        protected override void OnUpdate(float dt)
        {
            if (_entity == null)
            {
                return;
            }

            _speed = Mathf.Max(_speed + _acceleration * dt, 0);
            if (Mathf.Approximately(_speed, 0))
            {
                _entity.SyncEntityPos(_logicEndPosX, _logicEndPosY, _logicEndPosZ, 1f,
                    EntityMoveDirType.KeepRotation,
                    LevelMoveReason.Move);
                EndPush(_logicEndPosX, _logicEndPosY, _logicEndPosZ);
                return;
            }

            if (CollisionCheck())
            {
                EndPush((int) _curLogicPos.x, _logicEndPosY, (int) _curLogicPos.z);
                return;
            }

            _curLogicPos += _moveDir * (_speed * dt);
            _entity.SyncEntityPos((int) _curLogicPos.x, _logicEndPosY, (int) _curLogicPos.z, 1f,
                EntityMoveDirType.KeepRotation,
                LevelMoveReason.Move);
        }

        private bool CollisionCheck()
        {
            //当前实体（箱子）是一个可移动的阻挡物，也就是Recast agent，只能是胶囊（圆形）
            if (_collider is not CapsuleCollider capsuleCollider)
            {
                return false;
            }

            var halfHeight = capsuleCollider.height * 0.5f - capsuleCollider.radius;
            var start = capsuleCollider.bounds.center - new Vector3(0, halfHeight, 0);
            var end = capsuleCollider.bounds.center + new Vector3(0, halfHeight, 0);
            Physics.OverlapCapsuleNonAlloc(start, end, capsuleCollider.radius, _collisionResult,
                LayerMaskConsts.Layer_Entity);

            //判断挡在moveDir方向的collider
            foreach (var collider in _collisionResult)
            {
                if (collider == null)
                {
                    return false;
                }

                if (_collider == collider)
                {
                    continue;
                }

                //因为推动实体必须为capsule 碰撞体上离推动实体最近的点即为碰撞点
                var collisionPoint = collider.ClosestPoint(_collider.transform.position);
                var centerToCollisionPoint = collisionPoint - _collider.transform.position;
                //centerToCollisionPoint:推动实体中心到碰撞点的向量
                //_moveDir移动方向,y为0，做点积时会消掉centerToCollisionPoint的y分量
                //此时的计算等价于 推动实体圆心到碰撞点的向量与_moveDir点积
                //点积大于0表示夹角小于90度,做个图可以发现后续移动必定被阻碍
                if (Vector3.Dot(centerToCollisionPoint, _moveDir) > 0)
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// 获取推动方向
        /// 逻辑是看实体前后左右四个方向离x轴正方向最近的那个，选它作为推动四方向里的x轴正方向（相当于在原世界四方向里加入实体的偏移）
        /// 然后根据英雄和实体的位置，计算英雄->实体方向上，离四个方向最近的那个方向，作为实际推动方向
        /// </summary>
        /// <param name="dirPosX">世界x轴正方向</param>
        /// <param name="entityDir">实体朝向</param>
        /// <param name="heroToEntity">英雄->实体的方向</param>
        /// <returns></returns>
        private Vector2 GetPushDir(Vector2 dirPosX, Vector3 entityDir, Vector2 heroToEntity)
        {
            //东
            if (entityDir.x != 0 && entityDir.z != 0)
            {
                dirPosX = GetEntityClosestToEastDir(dirPosX, new Vector2(entityDir.x, entityDir.z));
            }

            //南
            var dirPosNegY = new Vector2(dirPosX.y, -dirPosX.x);
            //西
            var dirPosNegX = new Vector2(-dirPosX.x, -dirPosX.y);
            //北
            var dirPosY = new Vector2(-dirPosX.y, dirPosX.x);

            //夹角最小的dir为moveDir
            //先算东
            var maxDotProduct = Vector2.Dot(heroToEntity, dirPosX);
            var moveDir = dirPosX;
            //南
            var dotProduct = Vector2.Dot(heroToEntity, dirPosNegY);
            if (dotProduct > maxDotProduct)
            {
                maxDotProduct = dotProduct;
                moveDir = dirPosNegY;
            }

            //西
            dotProduct = Vector2.Dot(heroToEntity, dirPosNegX);
            if (dotProduct > maxDotProduct)
            {
                maxDotProduct = dotProduct;
                moveDir = dirPosNegX;
            }

            //北
            dotProduct = Vector2.Dot(heroToEntity, dirPosY);
            if (dotProduct > maxDotProduct)
            {
                moveDir = dirPosY;
            }

            return moveDir;
        }

        private static Vector2 GetEntityClosestToEastDir(Vector2 east, Vector2 entityDir)
        {
            var forward = entityDir;
            var left = new Vector2(forward.y, -forward.x);
            var back = new Vector2(-forward.x, -forward.y);
            var right = new Vector2(-forward.y, forward.x);
            //检查前方
            var maxDot = Vector2.Dot(forward, east);
            var closestDir = forward;
            // 检查左方
            var downDot = Vector2.Dot(left, east);
            if (downDot > maxDot)
            {
                maxDot = downDot;
                closestDir = left;
            }

            // 检查左方
            var leftDot = Vector2.Dot(back, east);
            if (leftDot > maxDot)
            {
                maxDot = leftDot;
                closestDir = back;
            }

            // 检查右方
            var rightDot = Vector2.Dot(right, east);
            if (rightDot > maxDot)
            {
                closestDir = right;
            }

            return closestDir;
        }
    }
}